<template>
  <view v-if="isShow" class="mxp-clipper" :class="[options.cusClass]" :style="[{ zIndex: options.zIndex }, options.cusStyle]">
    <view class="mxp-clipper-mask" @touchstart.stop.prevent="clipTouchStart" @touchmove.stop.prevent="clipTouchMove" @touchend.stop.prevent="clipTouchEnd">
      <view class="mxp-clipper-content" :style="clipStyle"><view v-for="(_item, index) in [1, 2, 3, 4]" :key="index" class="mxp-clipper-edge"></view></view>
    </view>
    <image
      v-if="data.image"
      class="mxp-clipper-image"
      :src="data.image"
      mode="scaleToFill"
      :style="imageStyle"
      @load="imageLoad"
      @touchstart="imageTouchStart"
      @touchmove="imageTouchMove"
      @touchend="imageTouchEnd"
    ></image>
    <canvas
      id="mxp-clipper"
      canvasId="mxp-clipper"
      disableScroll
      :style="'width: ' + data.canvasWidth * options.scaleRatio! + 'px; height:' + data.canvasHeight * options.scaleRatio! + 'px;'"
      class="mxp-clipper-canvas"
      type="2d"
    ></canvas>
    <view class="mxp-clipper-tools">
      <view class="mxp-clipper-tools-btns">
        <view v-if="options.isShowCancelBtn" @click="cancel">
          <slot name="cancel">
            <view class="cancel"><text>取消</text></view>
          </slot>
        </view>
        <view v-if="options.isShowPhotoBtn" key="view-2" @click="uploadImage">
          <slot name="photo">
            <image class="img" :src="photoUrl"></image>
          </slot>
        </view>
        <view v-if="options.isShowRotateBtn" key="view-3" @click="rotate">
          <slot name="rotate">
            <image class="img" :src="rotateUrl"></image>
          </slot>
        </view>
        <view v-if="options.isShowConfirmBtn" key="view-4" @click="confirm">
          <slot name="confirm">
            <mxp-button text="确定" width="80px" :disabled="loading" :loading="loading"></mxp-button>
          </slot>
        </view>
      </view>
    </view>
  </view>
</template>

<script lang="ts" setup>
  import {
    type Data,
    calcImageOffset,
    calcImageScale,
    calcImageSize,
    calcPythagoreanTheorem,
    clipTouchMoveOfCalculate,
    determineDirection,
    getData,
    imageTouchMoveOfCalcOffset,
  } from './utils';

  import photoUrl from './static/photo.svg';
  import rotateUrl from './static/rotate.svg';
  import { type CSSProperties, computed, getCurrentInstance, nextTick, reactive, ref } from 'vue';
  import MxpButton from '../mxp-button/mxp-button.vue';
  import type { ImageOnLoadEvent, TouchEvent } from 'mxp-uni-types';
  import mxpGetSysInfoStore from '../mxp-tool/store/mxp-store-sys-info';
  export type $ClipperProps = {
    /** 层级 */
    zIndex?: number; // eslint-disable-line vue/no-unused-properties
    /** 图片地址 */
    imageUrl?: string; // eslint-disable-line vue/no-unused-properties
    /** 返回图片的类型 */
    fileType?: string; // eslint-disable-line vue/no-unused-properties
    /** 清晰度 （0 到 1） */
    quality?: number; // eslint-disable-line vue/no-unused-properties
    /** 裁剪框宽度 */
    width?: number; // eslint-disable-line vue/no-unused-properties
    /** 裁剪框高度 */
    height?: number; // eslint-disable-line vue/no-unused-properties
    /** 裁剪框最小宽度 */
    minWidth?: number; // eslint-disable-line vue/no-unused-properties
    /** 裁剪框最大宽度 */
    maxWidth?: number; // eslint-disable-line vue/no-unused-properties
    /** 输出的图片的宽度 */
    destWidth?: number; // eslint-disable-line vue/no-unused-properties
    /** 输出的图片的高度 */
    destHeight?: number; // eslint-disable-line vue/no-unused-properties
    /** 裁剪框最小高度度 */
    minHeight?: number; // eslint-disable-line vue/no-unused-properties
    /** 裁剪框最大高度度 */
    maxHeight?: number; // eslint-disable-line vue/no-unused-properties
    /** 是否锁定裁剪框宽度 */
    isLockWidth?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 是否锁定裁剪框高度上 */
    isLockHeight?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 是否锁定裁剪框比例 */
    isLockRatio?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 生成图片相对于裁剪框的比例， 比例越高生成图片越清晰 */
    scaleRatio?: number; // eslint-disable-line vue/no-unused-properties
    /** 图片最小缩放比 */
    minRatio?: number; // eslint-disable-line vue/no-unused-properties
    /** 图片最大缩放比 */
    maxRatio?: number; // eslint-disable-line vue/no-unused-properties
    /** 是否禁止缩放 */
    isDisableScale?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 是否禁止旋转 */
    isDisableRotate?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 是否限制移动范围 */
    isLimitMove?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 是否显示选择图片按钮 */
    isShowPhotoBtn?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 是否显示旋转按钮 */
    isShowRotateBtn?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 是否显示确认按钮 */
    isShowConfirmBtn?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 是否显示取消按钮 */
    isShowCancelBtn?: boolean; // eslint-disable-line vue/no-unused-properties
    /** 旋转按钮每次旋转的角度 */
    rotateAngle?: number; // eslint-disable-line vue/no-unused-properties
    /** 选择来源 */
    source?: { album: string; camera: string }; // eslint-disable-line vue/no-unused-properties
    /** 自定义的样式属性 */
    cusStyle?: CSSProperties; // eslint-disable-line vue/no-unused-properties
    /** 自定义类名 */
    cusClass?: string; // eslint-disable-line vue/no-unused-properties
  };

  export type $CallBack = {
    submit?: (data: { url: string; width: number; height: number }) => void;
  };
  export type $Options = $CallBack & $ClipperProps;

  defineOptions({
    name: 'mxp-clipper',
  });

  const props = withDefaults(defineProps<$ClipperProps>(), {
    zIndex: 999,
    imageUrl: '',
    fileType: 'png',
    quality: 1,
    width: undefined,
    height: undefined,
    minWidth: 200,
    maxWidth: 600,
    destWidth: undefined,
    destHeight: undefined,
    minHeight: 200,
    maxHeight: 600,
    isLockWidth: false,
    isLockHeight: false,
    isLockRatio: true,
    scaleRatio: 1,
    minRatio: 0.5,
    maxRatio: 2,
    isDisableScale: false,
    isDisableRotate: false,
    isLimitMove: false,
    isShowPhotoBtn: true,
    isShowRotateBtn: true,
    isShowConfirmBtn: true,
    isShowCancelBtn: true,
    rotateAngle: 90,
    source: () => ({ album: '从相册中选择', camera: '拍照' }),
    cusStyle: () => ({}),
    cusClass: '',
  });
  defineSlots<{
    /** 自定义取消按钮 */
    cancel: () => void;
    /** 自定义拍照按钮 */
    photo: () => void;
    /** 自定义旋转按钮 */
    rotate: () => void;
    /** 自定义提交按钮 */
    confirm: () => void;
  }>();

  const emits = defineEmits<{
    (e: 'ready', res: { width: number; height: number }): void;
    (e: 'change', res: { width: number; height: number }): void;
    (e: 'rotate', res: number): void;
    (e: 'cancel'): void;
    (e: 'success', res: { url: string; width: number; height: number }): void;
    (e: 'fail', res: unknown): void;
  }>();

  const loading = ref(false);
  const instance = getCurrentInstance();
  const data: Data = reactive(getData());
  const options = ref<$Options>({ ...props });
  const isShow = ref(false);

  const clipStyle = computed(() => {
    const { clipWidth, clipHeight, clipY, clipX, animation } = data;
    return `
  		width: ${clipWidth}px;
  		height:${clipHeight}px;
  		transition-property: ${animation ? '' : 'background'};
  		left: ${clipX}px;
  		top: ${clipY}px
  		`;
  });

  const imageStyle = computed(() => {
    const { imageWidth, imageHeight, imageLeft, imageTop, animation, scale, angle } = data;
    return `
  			width: ${imageWidth ? `${imageWidth}px` : 'auto'};
  			height: ${imageHeight ? `${imageHeight}px` : 'auto'};
  			transform: translate3d(${imageLeft - imageWidth / 2}px, ${imageTop - imageHeight / 2}px, 0) scale(${scale}) rotate(${angle}deg);
  			transition-duration: ${animation ? 0.35 : 0}s
  		`;
  });

  /**
   * 显示
   */
  async function show(param: $Options = {}) {
    try {
      options.value = { ...props, ...param };
      data.sysinfo = mxpGetSysInfoStore().getSysInfo().value;
      options.value.width = options.value.width || Math.floor(Math.min(data.sysinfo.windowWidth, data.sysinfo.windowHeight) * 0.8);
      options.value.height = options.value.height || Math.floor(Math.min(data.sysinfo.windowWidth, data.sysinfo.windowHeight) * 0.8);

      await getImageInfo(options.value.imageUrl!);
      setClipInfo();
      setClipCenter();
      calcClipSize();
      cutDetectionPosition();

      nextTick(() => {
        isShow.value = true;
      });
    } catch (error) {
      console.log('打开编辑图片失败====', error);
    }
  }
  /** 隐藏 */
  function hide() {
    isShow.value = false;
  }

  /** 图片信息 */
  function getImageInfo(url: string) {
    return new Promise((resolve, reject) => {
      if (url) {
        data.imageInit = false;
        uni.getImageInfo({
          src: url,
          success: (res) => {
            if (['right', 'left'].includes(res.orientation || '')) {
              imgComputeSize(res.height, res.width);
            } else {
              imgComputeSize(res.width, res.height);
            }
            data.image = res.path;
            if (options.value.isLimitMove) {
              imgMarginDetectionScale();
              emits('ready', res);
            }
            resolve(true);
          },
          fail: (err) => {
            imgComputeSize(options.value.width!, options.value.height!);
            if (options.value.isLimitMove) {
              imgMarginDetectionScale();
            }
            reject(err);
          },
        });
      } else {
        reject(false);
      }
    });
  }

  /** 设置裁剪框的信息 */
  function setClipInfo() {
    const { width, height } = options.value;
    const { sysinfo, canvasId } = data;
    const clipWidth = width!;
    const clipHeight = height!;
    const clipY = (sysinfo.windowHeight - clipHeight) / 2;
    const clipX = (sysinfo.windowWidth - clipWidth) / 2;
    const imageLeft = sysinfo.windowWidth / 2;
    const imageTop = sysinfo.windowHeight / 2;
    data.ctx = uni.createCanvasContext(canvasId, instance);
    data.clipWidth = clipWidth;
    data.clipHeight = clipHeight;
    data.clipX = clipX;
    data.clipY = clipY;
    data.canvasHeight = clipHeight;
    data.canvasWidth = clipWidth;
    data.imageLeft = imageLeft;
    data.imageTop = imageTop;
  }

  /** 设置裁剪中心点 */
  function setClipCenter() {
    const { sysinfo, clipHeight, clipWidth, imageTop, imageLeft } = data;
    const sys = sysinfo || mxpGetSysInfoStore().getSysInfo().value;
    const clipY = (sys.windowHeight - clipHeight) * 0.5;
    const clipX = (sys.windowWidth - clipWidth) * 0.5;
    data.imageTop = imageTop - data.clipY + clipY;
    data.imageLeft = imageLeft - data.clipX + clipX;
    data.clipY = clipY;
    data.clipX = clipX;
  }

  /** 设置裁剪大小 */
  function calcClipSize() {
    const { clipHeight, clipWidth, sysinfo, clipX, clipY } = data;
    if (clipWidth > sysinfo.windowWidth) {
      data.clipWidth = sysinfo.windowWidth;
    } else if (clipWidth + clipX > sysinfo.windowWidth) {
      data.clipX = sysinfo.windowWidth - clipX;
    }
    if (clipHeight > sysinfo.windowHeight) {
      data.clipHeight = sysinfo.windowHeight;
    } else if (clipHeight + clipY > sysinfo.windowHeight) {
      data.clipY = sysinfo.windowHeight - clipY;
    }
  }

  /** 裁剪位置  */
  function cutDetectionPosition() {
    const { clipX, clipY, sysinfo, clipHeight, clipWidth } = data;
    const cutDetectionPositionTop = () => {
      if (clipY < 0) {
        data.clipY = 0;
      }
      if (clipY > sysinfo.windowHeight - clipHeight) {
        data.clipY = sysinfo.windowHeight - clipHeight;
      }
    };
    const cutDetectionPositionLeft = () => {
      if (clipX < 0) {
        data.clipX = 0;
      }
      if (clipX > sysinfo.windowWidth - clipWidth) {
        data.clipX = sysinfo.windowWidth - clipWidth;
      }
    };
    if (clipY === null && clipX === null) {
      const newClipY = (sysinfo.windowHeight - clipHeight) * 0.5;
      const newClipX = (sysinfo.windowWidth - clipWidth) * 0.5;
      data.clipX = newClipX;
      data.clipY = newClipY;
    } else if (clipY !== null && clipX !== null) {
      cutDetectionPositionTop();
      cutDetectionPositionLeft();
    } else if (clipY !== null && clipX === null) {
      cutDetectionPositionTop();
      data.clipX = (sysinfo.windowWidth - clipWidth) / 2;
    } else if (clipY === null && clipX !== null) {
      cutDetectionPositionLeft();
      data.clipY = (sysinfo.windowHeight - clipHeight) / 2;
    }
  }

  /** 计算图片大小 */
  function imgComputeSize(width: number, height: number) {
    const { imageWidth, imageHeight } = calcImageSize(width, height, data, { originWidth: options.value.width!, originHeight: options.value.height! });
    data.imageWidth = imageWidth;
    data.imageHeight = imageHeight;
  }

  /** bianju  */
  function imgMarginDetectionScale(scale?: number) {
    if (!options.value.isLimitMove) {
      return;
    }
    const currentScale = calcImageScale(data, scale);
    imgMarginDetectionPosition(currentScale);
  }
  /** s */
  function imgMarginDetectionPosition(scale: number) {
    if (!options.value.isLimitMove) {
      return;
    }
    const { scale: currentScale, left, top } = calcImageOffset(data, scale);
    data.imageLeft = left;
    data.imageTop = top;
    data.scale = currentScale;
  }
  /** s */
  function throttle() {
    data.throttleFlag = true;
  }
  /** s */
  function moveDuring() {
    data.timeClipCenter && clearTimeout(data.timeClipCenter);
  }
  /** s */
  function moveStop() {
    data.timeClipCenter && clearTimeout(data.timeClipCenter);
    const timeClipCenter = setTimeout(() => {
      if (!data.animation) {
        data.imageInit = true;
        data.animation = true;
      }
      setClipCenter();
    }, 800);
    data.timeClipCenter = timeClipCenter;
  }

  /** s */
  function clipTouchStart(event: TouchEvent) {
    // #ifdef H5
    event.preventDefault();
    // #endif
    const currentX = event.touches[0]!.clientX!;
    const currentY = event.touches[0]!.clientY!;
    const { clipX, clipY, clipWidth, clipHeight } = data;
    const corner = determineDirection(clipX, clipY, clipWidth, clipHeight, currentX, currentY);
    moveDuring();
    if (!corner) {
      return;
    }
    data.clipStart = {
      width: clipWidth,
      height: clipHeight,
      x: currentX,
      y: currentY,
      clipY,
      clipX,
      corner,
    };
    data.flagClipTouch = true;
    data.flagEndTouch = true;
  }
  /** s */
  function clipTouchMove(event: TouchEvent) {
    // #ifdef H5
    event.stopPropagation();
    event.preventDefault();
    // #endif
    // 只针对单指点击做处理
    if (event.touches.length !== 1) {
      return;
    }
    const { flagClipTouch, throttleFlag } = data;
    if (flagClipTouch && throttleFlag) {
      const { isLockRatio, isLockHeight, isLockWidth } = options.value;
      if (isLockRatio && (isLockWidth || isLockHeight)) {
        return;
      }
      data.throttleFlag = false;
      throttle();
      const clipData = clipTouchMoveOfCalculate(
        data,
        {
          isLockRatio: options.value.isLockRatio!,
          maxWidth: options.value.maxWidth!,
          minWidth: options.value.minWidth!,
          maxHeight: options.value.maxHeight!,
          minHeight: options.value.minHeight!,
        },
        event
      );
      if (clipData) {
        const { width, height, clipX, clipY } = clipData;
        if (!isLockWidth && !isLockHeight) {
          data.clipWidth = width;
          data.clipHeight = height;
          data.clipX = clipX;
          data.clipY = clipY;
        } else if (!isLockWidth) {
          data.clipWidth = width;
          data.clipX = clipX;
        } else if (!isLockHeight) {
          data.clipHeight = height;
          data.clipY = clipY;
        }
        imgMarginDetectionScale();
      }
    }
  }
  /** s */
  function clipTouchEnd() {
    moveStop();
    data.flagClipTouch = false;
  }
  /** s */
  function imageTouchStart(e: TouchEvent) {
    // #ifdef H5
    e.preventDefault();
    // #endif
    data.flagEndTouch = false;
    const { imageLeft, imageTop } = data;
    const clientXForLeft = e.touches[0]!.clientX!;
    const clientYForLeft = e.touches[0]!.clientY!;

    let touchRelative = [];
    if (e.touches.length === 1) {
      touchRelative[0] = {
        x: clientXForLeft - imageLeft,
        y: clientYForLeft - imageTop,
      };
      data.touchRelative = touchRelative;
    } else {
      const clientXForRight = e.touches[1]!.clientX || 0;
      const clientYForRight = e.touches[1]!.clientY || 0;
      const width = Math.abs(clientXForLeft - clientXForRight);
      const height = Math.abs(clientYForLeft - clientYForRight);
      const hypotenuseLength = calcPythagoreanTheorem(width, height);

      touchRelative = [
        {
          x: clientXForLeft - imageLeft,
          y: clientYForLeft - imageTop,
        },
        {
          x: clientXForRight - imageLeft,
          y: clientYForRight - imageTop,
        },
      ];
      data.touchRelative = touchRelative;
      data.hypotenuseLength = hypotenuseLength;
    }
  }
  /** s */
  function imageTouchMove(event: TouchEvent) {
    // #ifdef H5
    event.preventDefault();
    // #endif
    const { flagEndTouch, throttleFlag } = data;
    if (flagEndTouch || !throttleFlag) {
      return;
    }
    const clientXForLeft = event.touches[0]!.clientX!;
    const clientYForLeft = event.touches[0]!.clientY!;
    data.throttleFlag = false;
    throttle();
    moveDuring();
    if (event.touches.length === 1) {
      const { left: imageLeft, top: imageTop } = imageTouchMoveOfCalcOffset(data.touchRelative, clientXForLeft, clientYForLeft);
      data.imageLeft = imageLeft;
      data.imageTop = imageTop;
      imgMarginDetectionPosition(data.scale);
    } else {
      const clientXForRight = event.touches[1]!.clientX || 0;
      const clientYForRight = event.touches[1]!.clientY || 0;
      const width = Math.abs(clientXForLeft - clientXForRight);
      const height = Math.abs(clientYForLeft - clientYForRight);
      const hypotenuse = calcPythagoreanTheorem(width, height);
      let scale = data.scale * (hypotenuse / data.hypotenuseLength);
      if (options.value.isDisableScale) {
        scale = 1;
      } else {
        scale = scale <= options.value.minRatio! ? options.value.minRatio! : scale;
        scale = scale >= options.value.maxRatio! ? options.value.maxRatio! : scale;
        emits('change', {
          width: data.imageWidth * scale,
          height: data.imageHeight * scale,
        });
      }

      imgMarginDetectionScale(scale);
      data.hypotenuseLength = Math.sqrt(width ** 2 + height ** 2);
      data.scale = scale;
    }
  }
  /** s */
  function imageTouchEnd() {
    data.flagEndTouch = true;
    moveStop();
  }
  /** s */
  function uploadImage() {
    const itemList = Object.entries(options.value.source!);
    const sizeType = ['original', 'compressed'];
    const _uploadImage = (type: string) => {
      uni.chooseImage({
        count: 1,
        sizeType,
        sourceType: [type],
        success: ({ tempFilePaths }) => {
          data.image = tempFilePaths[0]!;
        },
      });
    };
    if (itemList.length > 1) {
      uni.showActionSheet({
        itemList: itemList.map((v) => v[1]),
        success: ({ tapIndex: i }) => {
          _uploadImage(itemList[i]![0]);
        },
      });
    } else {
      _uploadImage(itemList[0]![0]);
    }
  }
  /** s */
  function imageReset() {
    const sys = data.sysinfo || mxpGetSysInfoStore().getSysInfo().value;
    moveStop();
    data.scale = 1;
    data.angle = 0;

    data.imageTop = sys.windowHeight / 2;
    data.imageLeft = sys.windowWidth / 2;
  }
  /** 图片加载成功 */
  function imageLoad(e: ImageOnLoadEvent) {
    imageReset();
    emits('ready', { width: parseInt(e.detail.width!), height: parseInt(e.detail.height!) });
  }

  /**
   * 旋转
   */
  function rotate() {
    if (options.value.isDisableRotate) {
      return;
    }
    const { rotateAngle } = options.value;
    const originAngle = data.angle;

    data.angle = originAngle - rotateAngle!;
    emits('rotate', data.angle);
  }

  /**
   * 确认剪裁
   */
  function confirm() {
    loading.value = true;
    const { canvasHeight, canvasWidth, clipHeight, clipWidth, scale, ctx, imageLeft, imageTop, clipX, clipY, angle, image, canvasId } = data;
    const { scaleRatio: dpr, quality, fileType } = options.value;
    const draw = () => {
      const imageWidth = data.imageWidth * scale * dpr!;
      const imageHeight = data.imageHeight * scale * dpr!;
      const xpos = imageLeft - clipX;
      const ypos = imageTop - clipY;
      // const ctx = uni.createCanvasContext(canvasId, this);
      ctx!.translate(xpos * dpr!, ypos * dpr!);
      ctx!.rotate((angle * Math.PI) / 180);
      ctx!.drawImage(image, -imageWidth / 2, -imageHeight / 2, imageWidth, imageHeight);
      ctx!.draw(false, () => {
        const width = clipWidth * dpr!;
        const height = clipHeight * dpr!;

        uni.canvasToTempFilePath(
          {
            x: 0,
            y: 0,
            width,
            height,
            destWidth: options.value.destWidth || width,
            destHeight: options.value.destHeight || height,
            canvasId,
            fileType,
            quality,
            success: (res) => {
              loading.value = false;
              const _data = { url: res.tempFilePath, width, height };
              emits('success', _data);
              typeof options.value.submit === 'function' && options.value.submit(_data);
              hide();
            },
            fail: (error1: unknown) => {
              loading.value = false;
              console.error('error', error1);
              emits('fail', error1);
            },
          },
          instance
        );
      });
    };

    if (canvasWidth !== clipWidth || canvasHeight !== clipHeight) {
      data.canvasWidth = clipWidth;
      data.canvasHeight = clipHeight;
      ctx!.draw();
      nextTick(() => {
        setTimeout(() => {
          draw();
        }, 100);
      });
    } else {
      draw();
    }
  }
  /** 取消 */
  function cancel() {
    emits('cancel');
    isShow.value = false;
  }

  defineExpose({
    /** 显示 */
    show,
    /** 隐藏 */
    hide,
  });
</script>

<style lang="scss" scoped>
  $clipper-edge-border-width: 6rpx !default;
  $clipper-confirm-color: #07c160 !default;

  .flex-auto {
    flex: auto;
  }

  .bg-transparent {
    background-color: rgba(0, 0, 0, 0.9);
    transition-duration: 0.35s;
  }

  .mxp-clipper {
    position: fixed;
    top: var(--window-top);
    left: 0;
    z-index: 1;
    width: 100vw;
    height: calc(100vh - var(--window-top));
    background-color: rgba(0, 0, 0, 0.9);

    .mxp-clipper-mask {
      position: relative;
      z-index: 2;
      pointer-events: none;
    }

    .mxp-clipper-content {
      position: absolute;
      box-sizing: border-box;
      pointer-events: none;
      background: transparent;
      border: 1rpx solid rgba(255, 255, 255, 0.3);
      box-shadow: rgba(0, 0, 0, 0.5) 0 0 0 80vh;
      // transition-duration 0.35s
      // transition-property left,top
      &:before,
      &:after {
        position: absolute;
        content: '';
        border: 1rpx dashed rgba(255, 255, 255, 0.3);
      }

      &:before {
        top: 33.33%;
        width: 100%;
        height: 33.33%;
        border-right: none;
        border-left: none;
      }

      &:after {
        left: 33.33%;
        width: 33.33%;
        height: 100%;
        border-top: none;
        border-bottom: none;
      }
    }

    .mxp-clipper-edge {
      position: absolute;
      width: 34rpx;
      height: 34rpx;
      pointer-events: auto;
      border: $clipper-edge-border-width solid #ffffff;

      &:before {
        position: absolute;
        width: 40rpx;
        height: 40rpx;
        content: '';
        background-color: transparent;
      }

      &:nth-child(1) {
        top: -$clipper-edge-border-width;
        left: -$clipper-edge-border-width;
        border-right-width: 0 !important;
        border-bottom-width: 0 !important;

        &:before {
          top: -50%;
          left: -50%;
        }
      }

      &:nth-child(2) {
        top: -$clipper-edge-border-width;
        right: -$clipper-edge-border-width;
        border-bottom-width: 0 !important;
        border-left-width: 0 !important;

        &:before {
          top: -50%;
          left: 50%;
        }
      }

      &:nth-child(3) {
        bottom: -$clipper-edge-border-width;
        left: -$clipper-edge-border-width;
        border-top-width: 0 !important;
        border-right-width: 0 !important;

        &:before {
          bottom: -50%;
          left: -50%;
        }
      }

      &:nth-child(4) {
        right: -$clipper-edge-border-width;
        bottom: -$clipper-edge-border-width;
        border-top-width: 0 !important;
        border-left-width: 0 !important;

        &:before {
          bottom: -50%;
          left: 50%;
        }
      }
    }

    .mxp-clipper-image {
      position: absolute;
      top: 0;
      left: 0;
      z-index: 1;
      width: 100%;
      max-width: inherit;
      border-style: none;
      transform-origin: center;
      backface-visibility: hidden;
    }

    .mxp-clipper-canvas {
      position: fixed;
      top: -200vw;
      left: -200vw;
      z-index: 10;
      pointer-events: none;
    }

    .mxp-clipper-tools {
      position: absolute;
      bottom: 10px;
      left: 0;
      z-index: 99;
      width: 100%;
      color: #ffffff;

      .mxp-clipper-tools-btns {
        box-sizing: border-box;
        display: flex;
        align-items: center;
        justify-content: space-between;
        width: 100%;
        padding: 20rpx 40rpx;
        font-weight: bold;

        .cancel {
          width: 112rpx;
          height: 60rpx;
          line-height: 60rpx;
          text-align: center;
        }

        .confirm {
          width: 112rpx;
          height: 60rpx;
          line-height: 60rpx;
          text-align: center;
          background: var(--mxp-clipper-confirm-color, $clipper-confirm-color);
          border-radius: 6rpx;
        }

        .img {
          display: block;
          width: 60rpx;
          height: 60rpx;
        }
      }
    }
  }
</style>
